﻿using Newtonsoft.Json;
using System.Net.Http.Headers;
using System.Text.RegularExpressions;
using System.Web;

namespace GarminConnect
{
    public class HttpClientWrapper
    {
        private static readonly Regex CSRF_RE = new("name=\"_csrf\"\\s+value=\"(.+?)\"", RegexOptions.Compiled);
        private static readonly Regex TICKET_RE = new("ticket=([^\"]+)\"", RegexOptions.Compiled);
        private static readonly Regex ACCOUNT_LOCKED_RE = new("var statuss*=s*\"([^\"]*)\"", RegexOptions.Compiled);
        private static readonly Regex PAGE_TITLE_RE = new("<title>([^<]*)</title>", RegexOptions.Compiled);

        private const string USER_AGENT_CONNECTMOBILE = "com.garmin.android.apps.connectmobile";
        private const string USER_AGENT_BROWSER = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36";

        private const string OAUTH_CONSUMER_URL = "https://thegarth.s3.amazonaws.com/oauth_consumer.json";

        private readonly HttpClient _client;
        private readonly UrlClass _url;
        public Oauth1Token _oauth1Token = null!;
        public Oauth2Token _oauth2Token = null!;
        public Oauth1Consumer _oauthConsumer = null!;

        public HttpClientWrapper(UrlClass url)
        {
            _url = url;
            _client = new HttpClient(new HttpClientHandler() { UseCookies = true });
            _client.DefaultRequestHeaders.UserAgent.ParseAdd(USER_AGENT_BROWSER);
        }

        // 设置公共请求头
        public void SetCommonHeader(Dictionary<string, string> headers)
        {
            foreach (var kv in headers)
            {
                if (_client.DefaultRequestHeaders.Contains(kv.Key))
                    _client.DefaultRequestHeaders.Remove(kv.Key);
                _client.DefaultRequestHeaders.Add(kv.Key, kv.Value);
            }
        }

        // 获取 OAuth Consumer
        public async Task FetchOauthConsumerAsync()
        {
            var resp = await _client.GetStringAsync(OAUTH_CONSUMER_URL);
            var obj = JsonConvert.DeserializeObject<Dictionary<string, string>>(resp);
            _oauthConsumer = new Oauth1Consumer
            {
                Key = obj?["consumer_key"],
                Secret = obj?["consumer_secret"]
            };
        }

        // 检查 Token 是否过期
        public async Task CheckTokenValidAsync()
        {
            if (_oauth2Token != null)
            {
                if (_oauth2Token.ExpiresAt <= DateTimeOffset.UtcNow.ToUnixTimeMilliseconds())
                {
                    Console.Error.WriteLine("Token expired!");
                    await RefreshOauth2TokenAsync();
                }
            }
        }

        // Fix for CS8603: Possible null reference return  
        public async Task<T> GetAsync<T>(string url)
        {
            var request = new HttpRequestMessage(HttpMethod.Get, url);
            if (_oauth2Token != null)
            {
                request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _oauth2Token.AccessToken);
            }
            var response = await _client.SendAsync(request);
            if (!response.IsSuccessStatusCode)
            {
                HandleHttpError(response);
            }
            var content = await response.Content.ReadAsStringAsync();
            var result = JsonConvert.DeserializeObject<T>(content);
            return result == null ? throw new InvalidOperationException("Deserialization returned null.") : result;
        }

        /// <summary>
        /// 下载文件
        /// </summary>
        /// <param name="url"></param>
        /// <returns></returns>
        public async Task<byte[]> Download(string url)
        {
            // 发送 GET 请求
            using HttpResponseMessage response = await _client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);
            response.EnsureSuccessStatusCode();

            // 读取响应流
            byte[] fileBytes = await response.Content.ReadAsByteArrayAsync();
            return fileBytes;
        }

        // POST 请求
        public async Task<T> PostAsync<T>(string url, HttpContent content)
        {
            var request = new HttpRequestMessage(HttpMethod.Post, url);
            if (_oauth2Token != null)
            {
                request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _oauth2Token.AccessToken);
            }
            request.Content = content;
            var response = await _client.SendAsync(request);
            if (!response.IsSuccessStatusCode)
            {
                HandleHttpError(response);
            }
            var respContent = await response.Content.ReadAsStringAsync();
            var result = JsonConvert.DeserializeObject<T>(respContent);
            return result == null ? throw new InvalidOperationException("Deserialization returned null.") : result;
        }

        // DELETE 请求（通过 POST + X-Http-Method-Override）
        public async Task<T> DeleteAsync<T>(string url)
        {
            var request = new HttpRequestMessage(HttpMethod.Post, url);
            if (_oauth2Token != null)
            {
                request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _oauth2Token.AccessToken);
            }
            request.Headers.Add("X-Http-Method-Override", "DELETE");
            request.Content = new StringContent(string.Empty);
            var response = await _client.SendAsync(request);
            if (!response.IsSuccessStatusCode)
            {
                HandleHttpError(response);
            }
            var respContent = await response.Content.ReadAsStringAsync();
            var result = JsonConvert.DeserializeObject<T>(respContent);
            return result == null ? throw new InvalidOperationException("Deserialization returned null.") : result;
        }

        private void HandleHttpError(HttpResponseMessage response)
        {
            var status = (int)response.StatusCode;
            var statusText = response.ReasonPhrase;
            var data = response.Content.ReadAsStringAsync().Result;
            var msg = $"ERROR: ({status}), {statusText}, {data}";
            Console.Error.WriteLine(msg);
            throw new Exception(msg);
        }

        // 登录流程
        public async Task<HttpClientWrapper> LoginAsync(string username, string password)
        {
            await FetchOauthConsumerAsync();

            var ticket = await GetLoginTicketAsync(username, password);

            var oauth1 = await GetOauth1TokenAsync(ticket);

            await ExchangeAsync(oauth1);

            return this;
        }

        private async Task<string> GetLoginTicketAsync(string username, string password)
        {
            // Step1: Set cookie
            var step1Params = new Dictionary<string, string>
            {
                { "clientId", "GarminConnect" },
                { "locale", "en" },
                { "service", _url.GC_MODERN }
            };
            var step1Url = $"{_url.GARMIN_SSO_EMBED}?{BuildQueryString(step1Params)}";
            await _client.GetAsync(step1Url);

            // Step2: Get _csrf
            var step2Params = new Dictionary<string, string>
            {
                { "id", "gauth-widget" },
                { "embedWidget", "true" },
                { "locale", "en" },
                { "gauthHost", _url.GARMIN_SSO_EMBED }
            };
            var step2Url = $"{_url.SIGNIN_URL}?{BuildQueryString(step2Params)}";
            var step2Result = await _client.GetStringAsync(step2Url);
            var csrfMatch = CSRF_RE.Match(step2Result);
            if (!csrfMatch.Success)
                throw new Exception("login - csrf not found");
            var csrf_token = csrfMatch.Groups[1].Value;

            // Step3: Get ticket
            var signinParams = new Dictionary<string, string>
            {
                { "id", "gauth-widget" },
                { "embedWidget", "true" },
                { "clientId", "GarminConnect" },
                { "locale", "en" },
                { "gauthHost", _url.GARMIN_SSO_EMBED },
                { "service", _url.GARMIN_SSO_EMBED },
                { "source", _url.GARMIN_SSO_EMBED },
                { "redirectAfterAccountLoginUrl", _url.GARMIN_SSO_EMBED },
                { "redirectAfterAccountCreationUrl", _url.GARMIN_SSO_EMBED }
            };
            var step3Url = $"{_url.SIGNIN_URL}?{BuildQueryString(signinParams)}";

            var formData = new FormUrlEncodedContent(new[]
            {
                new KeyValuePair<string, string>("username", username),
                new KeyValuePair<string, string>("password", password),
                new KeyValuePair<string, string>("embed", "true"),
                new KeyValuePair<string, string>("_csrf", csrf_token)
            });

            var request = new HttpRequestMessage(HttpMethod.Post, step3Url)
            {
                Content = formData
            };
            request.Headers.Add("Dnt", "1");
            request.Headers.Add("Origin", _url.GARMIN_SSO_ORIGIN);
            request.Headers.Referrer = new Uri(_url.SIGNIN_URL);
            request.Headers.UserAgent.ParseAdd(USER_AGENT_BROWSER);

            var response = await _client.SendAsync(request);
            var step3Result = await response.Content.ReadAsStringAsync();

            HandleAccountLocked(step3Result);
            HandlePageTitle(step3Result);

            var ticketMatch = TICKET_RE.Match(step3Result);
            if (!ticketMatch.Success)
                throw new Exception("login failed (Ticket not found or MFA), please check username and password");

            return ticketMatch.Groups[1].Value;
        }

        private void HandlePageTitle(string htmlStr)
        {
            var match = PAGE_TITLE_RE.Match(htmlStr);
            if (match.Success)
            {
                var title = match.Groups[1].Value;
                Console.WriteLine($"login page title: {title}");
                if (title.Contains("Update Phone Number"))
                {
                    throw new Exception("login failed (Update Phone number), please update your phone number, currently I don't know where to update it");
                }
            }
        }

        private void HandleAccountLocked(string htmlStr)
        {
            var match = ACCOUNT_LOCKED_RE.Match(htmlStr);
            if (match.Success)
            {
                var msg = match.Groups[1].Value;
                Console.Error.WriteLine(msg);
                throw new Exception("login failed (AccountLocked), please open connect web page to unlock your account");
            }
        }

        public async Task RefreshOauth2TokenAsync()
        {
            if (_oauthConsumer == null)
            {
                await FetchOauthConsumerAsync();
            }
            if (_oauth2Token == null || _oauth1Token == null)
            {
                throw new Exception("No Oauth2Token or Oauth1Token");
            }

            var oauth = new OAuth1Client(_oauthConsumer!.Key!, _oauthConsumer!.Secret!, _oauth1Token!.OauthToken!, _oauth1Token!.OauthTokenSecret!);

            Oauth1 oauth1 = new()
            {
                Token = _oauth1Token,
                Oauth = oauth
            };

            await ExchangeAsync(oauth1);
            Console.WriteLine("Oauth2 token refreshed!");
        }

        private async Task<Oauth1> GetOauth1TokenAsync(string ticket)
        {
            if (_oauthConsumer == null || string.IsNullOrEmpty(_oauthConsumer.Key) || string.IsNullOrEmpty(_oauthConsumer.Secret))
                throw new Exception("Invalid OAUTH_CONSUMER: Key or Secret is null or empty.");

            

            var parameters = new Dictionary<string, string>
           {
               { "ticket", ticket },
               { "login-url", _url.GARMIN_SSO_EMBED },
               { "accepts-mfa-tokens", "true" }
           };
            var url = $"{_url.OAUTH_URL}/preauthorized?{BuildQueryString(parameters)}";

            var consumerKey = _oauthConsumer.Key ?? throw new InvalidOperationException("Consumer Key is null.");
            var consumerSecret = _oauthConsumer.Secret ?? throw new InvalidOperationException("Consumer Secret is null.");

            var oauthToken = _oauth1Token?.OauthToken ?? throw new InvalidOperationException("OauthToken is null.");
            var oauthTokenSecret = _oauth1Token?.OauthTokenSecret ?? throw new InvalidOperationException("OauthTokenSecret is null.");

            var oauth = new OAuth1Client(
                consumerKey,
                consumerSecret,
                oauthToken,
                oauthTokenSecret
            );

            var authHeader = oauth.GenerateAuthorizationHeader("GET", url, new Dictionary<string, string> ());

            var request = new HttpRequestMessage(HttpMethod.Get, url);
            request.Headers.Add("User-Agent", USER_AGENT_CONNECTMOBILE);
            request.Headers.Add("Authorization", authHeader);

            var response = await _client.SendAsync(request);
            var responseStr = await response.Content.ReadAsStringAsync();

            var tokenDict = HttpUtility.ParseQueryString(responseStr);
            oauthToken = tokenDict["oauth_token"] ?? throw new InvalidOperationException("OauthToken is null.");
            oauthTokenSecret = tokenDict["oauth_token_secret"] ?? throw new InvalidOperationException("OauthTokenSecret is null.");

            _oauth1Token = new Oauth1Token
            {
                OauthToken = oauthToken,
                OauthTokenSecret = oauthTokenSecret
            };


            Oauth1 oauth1 = new()
            {
                Token = _oauth1Token,
                Oauth = oauth
            };

            return oauth1;
        }

        public async Task ExchangeAsync(Oauth1 oauth1)
        {
            var baseUrl = $"{_url.OAUTH_URL}/exchange/user/2.0";

            // 构造请求数据
            var url = baseUrl+"?"+oauth1?.Oauth?.GenerateAuthorizationHeader("POST",baseUrl, new Dictionary<string, string>());

            // 发送 POST 请求，body 为空
            var request = new HttpRequestMessage(HttpMethod.Post, url);
            request.Headers.Add("User-Agent", USER_AGENT_CONNECTMOBILE);
            request.Content = new StringContent(string.Empty);
            request.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/x-www-form-urlencoded");

            var response = await _client.SendAsync(request);
            response.EnsureSuccessStatusCode();

            var responseContent = await response.Content.ReadAsStringAsync();
            var oauth2Token = JsonConvert.DeserializeObject<Oauth2Token>(responseContent) ?? throw new InvalidOperationException("Deserialization returned null for Oauth2Token.");
            // 设置过期时间
            _oauth2Token = SetOauth2TokenExpiresAt(oauth2Token);
        }

        private Oauth2Token SetOauth2TokenExpiresAt(Oauth2Token token)
        {
            var now = DateTime.UtcNow;
            var nowOffset = DateTimeOffset.UtcNow;
            token.LastUpdateDate = now.ToLocalTime().ToString();
            token.ExpiresDate = now.AddSeconds(token.ExpiresIn).ToLocalTime().ToString();
            token.ExpiresAt = nowOffset.AddSeconds(token.ExpiresIn).ToUnixTimeMilliseconds();
            token.RefreshTokenExpiresAt = nowOffset.AddSeconds(token.RefreshTokenExpiresIn).ToUnixTimeMilliseconds();
            return token;
        }

        private static string BuildQueryString(Dictionary<string, string> parameters)
        {
            var list = new List<string>();
            foreach (var kv in parameters)
            {
                list.Add($"{HttpUtility.UrlEncode(kv.Key)}={HttpUtility.UrlEncode(kv.Value)}");
            }
            return string.Join("&", list);
        }
    }
}
